/**
 * @file utils.c
 * Utility code for TrayInfo and its modules.
 */

/* Created by Laszlo Ashin <kodest@gmail.com> */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <stdarg.h>

/* needed for turning off echoing while get_pass() */
#include <termios.h>

#include "utils.h"

/**
 * Conversion table for base64 encoding.
 * @see base64_encode
 */
static char base64_table[65] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static int show_needs_separator;

#define SEP_SIZE	8
static char separator[SEP_SIZE] = " ";

/* ==== Configuration file parsing helper functions ==== */

int
cfgstr2bool(const char *s, int *bo)
{
	char *true[] = { "true", "yes", "enable", NULL };
	char *false[] = { "false", "no", "disable", NULL };
	int i;

	for (i = 0; true[i]; ++i)
		if (strcasecmp(s, true[i]) == 0) {
			*bo = 1;
			return 0;
		}

	for (i = 0; false[i]; ++i)
		if (strcasecmp(s, false[i]) == 0) {
			*bo = 0;
			return 0;
		}

	fprintf(stderr, "logical value was expected (got %s)\n", s);
	return !0;
}

int
cfgstr2int(const char *s, int *i)
{
	if (sscanf(s, "%d", i) != 1) {
		fprintf(stderr, "integer value was expected (got %s)\n", s);
		return !0;
	}
	return 0;
}

int
cfgstr2double(const char *s, double *d)
{
	if (sscanf(s, "%lf", d) != 1) {
		fprintf(stderr, "real value was expected (got %s)\n", s);
		return !0;
	}
	return 0;
}

/* ==== Data mining helper functions ==== */

int
collect_data_from_file(
	const char *fname, const char *sep, int num,
	const char *idlist[], char *valuelist[]
)
{
	char buf[256];
	FILE *fp = fopen(fname, "rt");
	int nread = 0;
	size_t seplen = strlen(sep);

	if (!fp) return nread;
	while (fgets(buf, (int)sizeof(buf), fp) == buf) {
		char *s, *t;
		int i;

		t = strstr(buf, sep);
		if (!t) continue;
		*t = '\0';
		s = t - 1;
		t += seplen;

		while (s >= buf && *s <= ' ') *s-- = '\0';
		for (s = buf; *s != '\0' && *s <= ' '; ++s);

		for (i = 0; i < num; ++i)
			if (strcmp(s, idlist[i]) == 0 && !valuelist[i])
				break;

		if (i >= num) continue;

		while (*t != '\0' && *t <= ' ') ++t;
		for (s = t + strlen(t) - 1; s >= t && *s <= ' '; *s-- = '\0');
		valuelist[i] = strdup(t);
		++nread;
		if (nread >= num) break;
	}
	(void)fclose(fp);
	return nread;
}

/*@null@*/ char *
base64_encode(const char *s, size_t len)
{
	size_t size = ((len + 2) / 3) * 4 + 1; /* +1: EOS */
	size_t k = len % 3;
	char *p, *buf = (char *)malloc(size);

	if (!buf) return NULL;
	p = buf;
	while (len > 0) {
		unsigned i = ((unsigned)(*s++)) << 16;
		if (len > 1) i |= ((unsigned)(*s++)) << 8;
		if (len > 2) i |= ((unsigned)(*s++));
		len = (len > 3) ? len - 3 : 0;
		*p++ = base64_table[(i >> 18) & 0x3f];
		*p++ = base64_table[(i >> 12) & 0x3f];
		*p++ = base64_table[(i >>  6) & 0x3f];
		*p++ = base64_table[ i        & 0x3f];
	}
	*p = '\0';
	if (k > 0) {
		k = 3 - k;
		while (k != 0) p[-k--] = '=';
	}
	return buf;
}

int
parse_prefixed_string(const char *s, double *f, char **endpos)
{
	char *t;
	char mul[] = "kMGT";
	char div[] = "munp";

	if (!f) {
		return !0;
	}
	*f = strtod(s, &t);
	if (t == s) {
		*endpos = t;
		return !0;
	}
	while (*t != '\0' && *t <= ' ') {
		++t;
	}
	if (*t != '\0') {
		char *found;

		found = strchr(mul, *t);
		if (found) {
			++t;
			while (found <= mul) {
				*f *= 1024.0f;
				++found;
			}
		} else {
			found = strchr(div, *t);
			if (found) {
				++t;
				while (found <= div) {
					*f /= 1024.0f;
					++found;
				}
			}
		}
	}
	*endpos = t;
	return 0;
}

ssize_t
get_pass(char *buf, size_t bufsize)
{
	struct termios told, tnew;
	char *s;

	if (!buf) {
		return -1;
	}
	
	/* turn off echoing or return with error */
	if (tcgetattr(0, &told) != 0) {
		return -1;
	}
	tnew = told;
	tnew.c_lflag &= ~ECHO;
	if (tcsetattr(0, TCSAFLUSH, &tnew) != 0) {
		return -1;
	}

	/* safely input password */
	s = fgets(buf, (int)bufsize, stdin);

	/* turn back echoing on */
	(void)tcsetattr(0, TCSAFLUSH, &told);

	if (s != buf || *buf == '\0') {
		return -1;
	}
	s = buf + strlen(buf) - 1;
	if (*s != '\n') {
		return -1;
	}
	*s = '\0';
	(void)puts("");
	return (ssize_t)strlen(buf);
}

double
gettimeofdayd(void)
{
	struct timeval tv;

	if (gettimeofday(&tv, NULL) == 0) {
		return (double)tv.tv_sec + (0.000001 * tv.tv_usec);
	} else {
		return 0.0f;
	}
}

void
double2timeval(double d, /*@out@*/ struct timeval *tv)
{
	if (d < 0.0f) {
		d = 0.0f;
	}
	tv->tv_sec = (long int)d;
	tv->tv_usec = (long int)((d - tv->tv_sec) * 1000000.0f);
}

int
set_separator(const char *s)
{
	strncpy(separator, s, SEP_SIZE - 1);
	separator[SEP_SIZE - 1] = '\0';
	return 0;
}

int
show(const char *fmt, ...)
{
	va_list ap;
	int n, k = 0;

	if (show_needs_separator != 0) {
		k = (int)fwrite(separator, 1, strlen(separator), stdout);
	}
	va_start(ap, fmt);
	n = vprintf(fmt, ap);
	va_end(ap);
	show_needs_separator = n;
	return n + k;
}

void
show_new_line(void)
{
	show_needs_separator = 0;
	(void)puts("");
}

void
show_error(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	(void)vfprintf(stderr, fmt, ap);
	va_end(ap);
	(void)fputc('\n', stderr);
}

/*@only@*/ /*@null@*/ void *
xalloc(size_t size)
{
	void *p = calloc(1, size);

	if (p == NULL) {
		show_error("Unable to allocate %d bytes.", size);
	}
	return p;
}
